package cn.stalk.compare;

import cn.stalk.compare.result.ComparisonPair;
import cn.stalk.compare.result.ComparisonResult;
import cn.stalk.compare.result.DifferencePair;
import cn.stalk.compare.result.DifferenceValue;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 数据比较器<br>
 * 需要设置数据的唯一键访问器setDataAccessorList<br>
 * 需要设置数据的比较数据访问器setDataAccessorList<br>
 */
public class DataComparator<T> {

    private LinkedHashMap<String, DataAccessor<T, Object>> keyDataAccessorMap = new LinkedHashMap<>();
    private Set<DataAccessor<T, Object>> dataAccessorList = new HashSet<>();

    public DataComparator(LinkedHashSet<DataAccessor<T, Object>> keyDataAccessors, Set<DataAccessor<T, Object>> dataAccessorList) {
        this.dataAccessorList = dataAccessorList;
        addKeyDataAccessors(keyDataAccessors);
    }

    public DataComparator() {
    }

    public Set<DataAccessor<T, Object>> getDataAccessorList() {
        return dataAccessorList;
    }

    public LinkedHashMap<String, DataAccessor<T, Object>> getKeyDataAccessorMap() {
        return keyDataAccessorMap;
    }

    public void setDataAccessorList(Set<DataAccessor<T, Object>> dataAccessorList) {
        this.dataAccessorList = dataAccessorList;
    }

    public void setKeyDataAccessorMap(LinkedHashMap<String, DataAccessor<T, Object>> keyDataAccessorMap) {
        this.keyDataAccessorMap = keyDataAccessorMap;
    }

    public void replaceKeyDataAccessor(DataAccessor<T, Object> keyDataAccessor) {
        keyDataAccessorMap.put(keyDataAccessor.getName(), keyDataAccessor);
    }

    public void replaceDataAccessor(DataAccessor<T, Object> dataAccessor) {
        dataAccessorList.remove(dataAccessor);
        dataAccessorList.add(dataAccessor);
    }

    public void addKeyDataAccessors(Collection<DataAccessor<T, Object>> accessors) {
        accessors.forEach(accessor -> keyDataAccessorMap.put(accessor.getName(), accessor));
    }

    public void removeDataAccessor(String name) {
        dataAccessorList.removeIf(e -> e.getName().equals(name));
    }

    /**
     * 包装后的对象比较<br>
     * 前提：两对象key认为是一致的
     */
    private void doCompare(ComparisonResult<T> result, DataWrapper<T> sourceWrapper, DataWrapper<T> targetWrapper) {
        DifferencePair<T> detail = new DifferencePair<>(sourceWrapper.keys, sourceWrapper.value, targetWrapper.value);
        if (null != dataAccessorList && !dataAccessorList.isEmpty()) {
            Object sourceValue = null, targetValue = null;
            for (DataAccessor<? super T, Object> dataAccessor : dataAccessorList) {
                sourceValue = dataAccessor.getValue(sourceWrapper.value);
                targetValue = dataAccessor.getValue(targetWrapper.value);
                if (null == sourceValue && null == targetValue) { // 双方值都为null，跳过
                    continue;
                }
                if (null == sourceValue || null == targetValue || !dataAccessor.equals(sourceValue, targetValue)) {
                    detail.addDifference(dataAccessor.getName(), new DifferenceValue(sourceValue, targetValue));
                }
            }
        }
        if (detail.hasDifference()) {
            result.getChanged().add(detail);
        } else {
            result.getSames().add(detail);
        }
    }

    protected ComparisonResult<T> doCompare(List<DataWrapper<T>> sourceWrapperList, List<DataWrapper<T>> targetWrapperList) {
        ComparisonResult<T> result = new ComparisonResult<>();
        // 保证顺序，使用LinkedHashMap

        Map<Object, List<DataWrapper<T>>> sourceMap = sourceWrapperList.stream().collect(Collectors.groupingBy(e -> e.getId(), LinkedHashMap::new, Collectors.toList()));
        Map<Object, List<DataWrapper<T>>> targetMap = targetWrapperList.stream().collect(Collectors.groupingBy(e -> e.getId(), LinkedHashMap::new, Collectors.toList()));
        sourceMap.forEach((k, list) -> {
            boolean sourceDuplicate = list.size() > 1;
            if (sourceDuplicate) {
                result.getDuplicateSourceKeys().add(k);
            }
            List<T> tmpSourceList = list.stream().map(e -> e.value).collect(Collectors.toList());
            List<DataWrapper<T>> tmpList = targetMap.remove(k);
            if (null != tmpList) {
                boolean targetDuplicate = tmpList.size() > 1;
                if (targetDuplicate) {
                    result.getDuplicateTargetKeys().add(k);
                }
                if (sourceDuplicate || targetDuplicate) {
                    List<T> tmpTargetList = tmpList.stream().map(e -> e.value).collect(Collectors.toList());
                    result.getInvalid().add(new ComparisonPair<>((Map<String, Object>) k, tmpSourceList, tmpTargetList));
                } else {
                    doCompare(result, list.get(0), tmpList.get(0));
                }
            } else {
                result.getRemainingSources().put(k, tmpSourceList);
            }
        });
        targetMap.forEach((k, list) -> {
            if (list.size() > 1) {
                result.getDuplicateTargetKeys().add(k);
            }
            List<T> tmpTargetList = list.stream().map(e -> e.value).collect(Collectors.toList());
            result.getRemainingTargets().put(k, tmpTargetList);
        });
        return result;
    }

    /**
     * 两组对象比较。<br/>
     * 先用原数据匹配对比数据，匹配不了主键时，会生成一条对比结果<br/>
     * 再用对比数据匹配原数据，匹配不了时，亦会生成一条对比结果<br/>
     * 匹配上主键时，对比其他字段信息，有差异则生成一条对比结果<br/>
     * 切记：对比前，原数据、对比数据需要按主键去重，若不去重则使用最开始匹配的数据
     *
     * @param sourceList 原数据
     * @param targetList 对比数据
     */
    public ComparisonResult<T> compare(Collection<T> sourceList, Collection<T> targetList) {
        if (keyDataAccessorMap.isEmpty()) {
            throw new RuntimeException("no key define in comparator.");
        }
        List<DataWrapper<T>> sourceWrapperList = sourceList.stream().map(e -> new DataWrapper<T>(e, keyDataAccessorMap)).collect(Collectors.toList());
        List<DataWrapper<T>> targetWrapperList = targetList.stream().map(e -> new DataWrapper<T>(e, keyDataAccessorMap)).collect(Collectors.toList());
        return doCompare(sourceWrapperList, targetWrapperList);
    }

    public static <T> ComparisonResult<T> compare(T source, T target, Function<T, Object>... compareColumns) {
        DataComparator<T> comparator = new DataComparator<>();
        Set<DataAccessor<T, Object>> sets = new HashSet<>();
        for (int i = 0; i < compareColumns.length; i++) {
            sets.add(new FunctionDataAccessor<>(String.valueOf(i), compareColumns[i]));
        }
        comparator.setDataAccessorList(sets);
        return comparator.compare(source, target);
    }

    /**
     * 忽略主键，直接比较属性<br>
     * 前提：两对象都不可为空
     */
    public ComparisonResult<T> compare(T source, T target) {
        ComparisonResult<T> result = new ComparisonResult<>();
        if (null == source && null == target) {
            return result;
        }
        if (null == target) {
            result.getRemainingSources().put(null, Arrays.asList(source));
        } else if (null == source) {
            result.getRemainingTargets().put(null, Arrays.asList(target));
        } else {
            doCompare(result, new DataWrapper<T>(source), new DataWrapper<T>(target));
        }
        return result;
    }

    static class FunctionDataAccessor<T> extends DataAccessor<T, Object> {
        final Function<T, Object> getter;

        public FunctionDataAccessor(String name, Function<T, Object> getter) {
            super(name);
            this.getter = getter;
        }

        @Override
        public Object apply(T t) {
            return getter.apply(t);
        }
    }

}
